
//
// 101203 - AH - (Fantoft, Block E) Created.
// 101205 - AH - Renamed from ForestMatched.
// 101206 - AH - (Bien) methods for retrieval strategy algorithms.
// 101208 - AH - Moved from org.soekrat.engine.wheel
// 110113 - AH - Made package scope only and removed old main test program.
// 110115 - AH - Minor java doc fixes.
// 110511 - AH - (Chaos) Fixed that tree node name matching was case sensitive,
//               should not be.
// 110606 - AH - (Garage) Changes related to that tree now has SoekratName.
// 111002 - AH - Improved doc.
//

package org.soekrat.engine.retrieval.forest;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.soekrat.engine.SoekratName;
import org.soekrat.engine.SoekratNameMatcher;
import org.soekrat.engine.SoekratNameMatcherSingleton;
import org.soekrat.engine.forest.Forest;
import org.soekrat.engine.forest.Tree;

/**
 * <p>
 *   Information on how a keyword matched in the forest.
 * </p>
 * @author Laetitia Le Chatton (LLC) 
 * @author Arne Halvorsen (AH) 
 */
final class ForestScanDetail 
{
  private final String m_keyword; // Keyword that was matched in forest.
  
  /**
   * <p>
   *   Identify the location the keyword matched in a tree.
   * </p>
   * @author Arne Halvorsen (AH)
   * @author Laetitia Le Chatton (LLC) 
   */
  final static class Location
  {
    /**
     * <p>
     *   Root of the tree keyword was matched im.
     * </p>
     */
    private final Tree m_root;
    
    /**
     * <p>
     *   The node keyword matched.
     * </p>
     */
    private final Tree m_node;
    
    // Constructor.
    private Location(Tree root, Tree node){ m_root=root; m_node=node; }
    
    /**
     * <p>
     *   Tells if this location is a root match.
     * </p>
     * @return {@code true} if is case, {@code false} if is not.
     */
    private boolean isRootMatched(){ return m_root==m_node; }
    
    /**
     * <p>
     *   Tells if a children has been matched, one that is not the root.
     * </p>
     * @return {@code true} if is the case, {@code false} if is not the case.
     */
    private boolean isChildMatched(){ return m_root!=m_node; }
    
    @Override
    public String toString() 
    {
      StringBuilder sb=new StringBuilder();
      
      sb.append("root=").append(m_root.getName());
      
      Tree[] path=m_node.path();
      
      sb.append(" path=").append(Arrays.toString(path));
      
      return sb.toString();
    }
  }
  
  private List<Location> m_locations=new ArrayList<Location>();
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param keyword Keyword being matched.
   */
  ForestScanDetail(String keyword)
  {
    if (keyword==null)
    {
      throw new NullPointerException("keyword");
    }
    
    m_keyword=keyword;
  }
  
  /**
   * <p>
   *   Gets number of keywords matched.
   * </p>
   * @return Count.
   */
  int getNumberOfMatches(){ return m_locations.size(); }
  
  /**
   * <p>
   *   Finds matched for {@code this}' keyword.
   * </p>
   * @param forest {@link Forest} to look in.
   */
  void match(Forest forest)
  {
    if (forest==null)
    {
      throw new NullPointerException("forest");
    }
    
    SoekratNameMatcher matcher=SoekratNameMatcherSingleton.INSTANCE;
    
    for (Tree tree : forest) match(tree, matcher);
  }
  
  private void match(Tree tree, SoekratNameMatcher matcher)
  {
    if (tree==null)
    {
      throw new NullPointerException("tree");
    }
    
    match(tree, tree, matcher);
  }
  
  private void match(Tree root, Tree node, SoekratNameMatcher matcher)
  {
    SoekratName name=node.getSoekratName();
    
    if (matcher.match(m_keyword, name))
    {
      Location loc=new Location(root, node);
      m_locations.add(loc);      
    }
    
    for (Tree child : node) match(root, child, matcher);
  }
  
  @Override
  public String toString() 
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    for (Location loc : m_locations)
    {
      pw.println(loc);
    }
    
    pw.flush();
    return caw.toString();
  }
  
  //
  // Methods that help implementing pseudo code concepts at ForestScan level.  
  //
  
  /**
   * <p>
   *   The deepest matched tree for where the keyword this represents matched 
   *   the deepest.
   * </p>
   * @return Tree found. 
   */
  Tree getDeepest()
  { 
    int depth=0;
    Tree bottom=null;
    for (Location curr : m_locations)
    {
      int d=curr.m_node.path().length;
      if (d>depth) 
      {
        depth=d;
        bottom=curr.m_node;
      }
    }
    
    return bottom;
  }
  
  /**
   * <p>
   *   Append all matched (key) to {@code found}.
   * </p>
   * @param found List to append to.
   */
  void getKey(List<Tree> found)
  {
    if (found==null)
    {
      throw new NullPointerException("found");
    }
    
    for (Location loc : m_locations) found.add(loc.m_node);  
  }
  
  /**
   * <p>
   *   Tells if keyword this represents only matched roots.
   * </p>
   * @return {@code true} if is case, {@code false} if is not the case.
   */
  boolean onlyRoot()
  {
    boolean m_rootMatched=false;
    boolean m_childrenMatched=false;
    
    for (Location loc : m_locations)
    {
      if (loc.isRootMatched()) m_rootMatched=true;
      else                     m_childrenMatched=true;
    }
    
    return m_rootMatched==true && m_childrenMatched==false;
  }
  
  /**
   * <p>
   *   Tells if a children has been matched.
   * </p>
   * @return {@code true} if not the case {@code false}.  
   */
  boolean matchChild()
  {
    for (Location loc : m_locations)
    {
      if (loc.isChildMatched()) return true;
    }
    return false;
  }
  
  /**
   * <p>
   *   Tells if only one child was matched (not a root) was matched.
   * </p>
   * @return {@code true} if is case, {@code false} if not the case.
   */
  boolean oneChildMatch()
  {
    int count=0;
    for (Location curr : m_locations)
    {
      if (curr.isChildMatched()) count++;
      else                       return false;
      if (count>1) return false;
    }
    return true;
  }
  
}
